/*******************************************************************************
 * Copyright (c) 2022-09-02 @author <a href="mailto:iffiff1@gmail.com">Tyler Chen</a>.
 * All rights reserved.
 *
 * Contributors:
 *     <a href="mailto:iffiff1@gmail.com">Tyler Chen</a> - initial API and implementation.
 ******************************************************************************/
package org.iff.util;

import org.iff.util.Utils;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * XmlMap
 *
 * @author <a href="mailto:iffiff1@gmail.com">Tyler Chen</a>
 * @since 2022-09-02
 */
public class XmlLinkedMap extends LinkedHashMap<String, Object> {
    private static final XmlLinkedMap empty = new XmlLinkedMap();

    public XmlLinkedMap() {
    }

    public XmlLinkedMap(Map<? extends String, ?> m) {
        super(m);
    }

    public String tag() {
        return (String) get("@tag");
    }

    public XmlLinkedMap tag(String tag) {
        put("@tag", tag);
        return this;
    }

    public Integer id() {
        return (Integer) get("@id");
    }

    public XmlLinkedMap id(Integer id) {
        put("@id", id);
        return this;
    }

    public Integer pid() {
        return (Integer) get("@pid");
    }

    public XmlLinkedMap pid(Integer pid) {
        put("@pid", pid);
        return this;
    }

    public String value() {
        return Optional.ofNullable((String) get("@value")).orElse("");
    }

    public XmlLinkedMap value(String value) {
        put("@value", value);
        return this;
    }

    public <T> T attr(String name) {
        return (T) get(name);
    }

    public XmlLinkedMap attr(String name, Object value) {
        put(name, value);
        return this;
    }

    public List<XmlLinkedMap> children() {
        return (List<XmlLinkedMap>) get("@children");
    }

    public XmlLinkedMap children(List<XmlLinkedMap> children) {
        put("@children", children);
        return this;
    }

    public XmlLinkedMap children(XmlLinkedMap... children) {
        put("@children", Utils.toList(children));
        return this;
    }

    public XmlLinkedMap removeChildren() {
        remove("@children");
        return this;
    }

    public boolean isTag(String tag) {
        return tag == null ? false : tag.equals(tag());
    }

    public boolean hasAttr(String attrName) {
        return containsKey(attrName);
    }

    public boolean isValue() {
        return !containsKey("@tag");
    }

    public boolean hasChild(String tagName) {
        return filterFirst(child -> child.isTag(tagName)) != null;
    }

    public XmlLinkedMap filterFirstNotNull(Predicate<XmlLinkedMap> predicate) {
        return Optional.ofNullable(filterFirst(predicate)).orElse(empty);
    }

    public XmlLinkedMap filterFirst(Predicate<XmlLinkedMap> predicate) {
        List<XmlLinkedMap> result = new ArrayList<>();
        Consumer<XmlLinkedMap>[] scan = new Consumer[1];
        scan[0] = (xmlMap) -> {
            List<XmlLinkedMap> children = Optional.ofNullable(xmlMap.children()).orElse(new ArrayList<>());
            for (XmlLinkedMap child : children) {
                if (predicate.test(child)) {
                    result.add(child);
                    break;
                }
                scan[0].accept(child);
            }
            if (result.size() > 0) {
                return;
            }
        };
        scan[0].accept(this);
        return result.isEmpty() ? null : result.get(0);
    }

    public List<XmlLinkedMap> filter(Predicate<XmlLinkedMap> predicate, AtomicBoolean stopMark) {
        List<XmlLinkedMap> result = new ArrayList<>();
        Consumer<XmlLinkedMap>[] scan = new Consumer[1];
        scan[0] = (xmlMap) -> {
            List<XmlLinkedMap> children = Optional.ofNullable(xmlMap.children()).orElse(new ArrayList<>());
            for (XmlLinkedMap child : children) {
                if (predicate.test(child)) {
                    result.add(child);
                }
                if (stopMark.get()) {
                    return;
                }
                scan[0].accept(child);
            }
            if (stopMark.get()) {
                return;
            }
        };
        scan[0].accept(this);
        return result;
    }

    /**
     * 查询子节点，stopMark=true，则所有的后续节点都不再查找；stopChild=true，则本节点的子节点不再查找并且跳过子节点后自动设置为 false
     */
    public List<XmlLinkedMap> filter(Predicate<XmlLinkedMap> predicate, AtomicBoolean stopMark, AtomicBoolean stopChild) {
        List<XmlLinkedMap> result = new ArrayList<>();
        Consumer<XmlLinkedMap>[] scan = new Consumer[1];
        scan[0] = (xmlMap) -> {
            List<XmlLinkedMap> children = Optional.ofNullable(xmlMap.children()).orElse(new ArrayList<>());
            for (XmlLinkedMap child : children) {
                if (predicate.test(child)) {
                    result.add(child);
                }
                if (stopMark.get()) {
                    return;
                }
                if (stopChild.get()) {
                    stopChild.set(false);
                } else {
                    scan[0].accept(child);
                }
            }
            if (stopMark.get()) {
                return;
            }
        };
        scan[0].accept(this);
        return result;
    }

    public void scan(Consumer<XmlLinkedMap> consumer) {
        Optional.ofNullable(children()).orElse(new ArrayList<>()).forEach(consumer);
    }

    public void scanAttr(Consumer<String> consumer) {
        keySet().stream().filter(key -> key.length() < 1 || key.charAt(0) != '@').forEach(consumer);
    }

    public static XmlLinkedMap parent(Map<Integer, XmlLinkedMap> root, Integer id) {
        XmlLinkedMap xmlMap = root == null || id == null ? null : root.get(id);
        if (xmlMap != null) {
            return root.get(xmlMap.pid());
        }
        return null;
    }

    public static XmlLinkedMap parentToTag(Map<Integer, XmlLinkedMap> root, Integer id, String tagName) {
        XmlLinkedMap parent = parent(root, id);
        while (parent != null) {
            if (parent.isTag(tagName)) {
                return parent;
            }
            parent = parent(root, parent.id());
        }
        return null;
    }

    private static void convert(Object xmlMapOrList) {
        if (xmlMapOrList instanceof List) {
            List list = (List) xmlMapOrList;
            for (int i = 0; i < list.size(); i++) {
                Object obj = list.get(i);
                if (obj instanceof Map) {
                    if (obj instanceof XmlLinkedMap) {
                        convert((XmlLinkedMap) obj);
                    } else {
                        XmlLinkedMap value = new XmlLinkedMap((Map) obj);
                        list.set(i, value);
                        convert(value);
                    }
                } else if (obj instanceof List) {
                    convert((List) obj);
                }
            }
        } else if (xmlMapOrList instanceof XmlLinkedMap) {
            XmlLinkedMap xmlMap = (XmlLinkedMap) xmlMapOrList;
            xmlMap.forEach((k, v) -> {
                if (v instanceof Map) {
                    if (v instanceof XmlLinkedMap) {
                        convert((XmlLinkedMap) v);
                    } else {
                        XmlLinkedMap value = new XmlLinkedMap((Map) v);
                        xmlMap.put(k, value);
                        convert(value);
                    }
                } else if (v instanceof List) {
                    convert((List) v);
                }
            });
        }
    }

    public static List<XmlLinkedMap> transform(List<Map> list) {
        if (list == null) {
            return null;
        }
        List result = new ArrayList(list);
        convert(result);
        return result;
    }

    public static XmlLinkedMap transform(Map map) {
        if (map == null) {
            return null;
        }
        XmlLinkedMap xmlMap = new XmlLinkedMap(map);
        convert(xmlMap);
        return xmlMap;
    }
}
